{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Deep Learning Models -- A collection of various deep learning architectures, models, and tips for TensorFlow and PyTorch in Jupyter Notebooks.\n",
    "- Author: Sebastian Raschka\n",
    "- GitHub Repository: https://github.com/rasbt/deeplearning-models"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sebastian Raschka \n",
      "\n",
      "CPython 3.6.8\n",
      "IPython 7.2.0\n",
      "\n",
      "torch 1.0.0\n"
     ]
    }
   ],
   "source": [
    "%load_ext watermark\n",
    "%watermark -a 'Sebastian Raschka' -v -p torch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Runs on CPU or GPU (if available)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Model Zoo -- Convolutional Autoencoder with Nearest-neighbor Interpolation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A convolutional autoencoder using nearest neighbor upscaling layers that compresses 768-pixel MNIST images down to a 7x7x8 (392 pixel) representation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "import numpy as np\n",
    "import torch\n",
    "import torch.nn.functional as F\n",
    "from torch.utils.data import DataLoader\n",
    "from torchvision import datasets\n",
    "from torchvision import transforms\n",
    "\n",
    "\n",
    "if torch.cuda.is_available():\n",
    "    torch.backends.cudnn.deterministic = True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Device: cuda:0\n",
      "Image batch dimensions: torch.Size([128, 1, 28, 28])\n",
      "Image label dimensions: torch.Size([128])\n"
     ]
    }
   ],
   "source": [
    "##########################\n",
    "### SETTINGS\n",
    "##########################\n",
    "\n",
    "# Device\n",
    "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n",
    "print('Device:', device)\n",
    "\n",
    "# Hyperparameters\n",
    "random_seed = 123\n",
    "learning_rate = 0.05\n",
    "num_epochs = 10\n",
    "batch_size = 128\n",
    "\n",
    "\n",
    "##########################\n",
    "### MNIST DATASET\n",
    "##########################\n",
    "\n",
    "# Note transforms.ToTensor() scales input images\n",
    "# to 0-1 range\n",
    "train_dataset = datasets.MNIST(root='data', \n",
    "                               train=True, \n",
    "                               transform=transforms.ToTensor(),\n",
    "                               download=True)\n",
    "\n",
    "test_dataset = datasets.MNIST(root='data', \n",
    "                              train=False, \n",
    "                              transform=transforms.ToTensor())\n",
    "\n",
    "\n",
    "train_loader = DataLoader(dataset=train_dataset, \n",
    "                          batch_size=batch_size, \n",
    "                          shuffle=True)\n",
    "\n",
    "test_loader = DataLoader(dataset=test_dataset, \n",
    "                         batch_size=batch_size, \n",
    "                         shuffle=False)\n",
    "\n",
    "# Checking the dataset\n",
    "for images, labels in train_loader:  \n",
    "    print('Image batch dimensions:', images.shape)\n",
    "    print('Image label dimensions:', labels.shape)\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "##########################\n",
    "### MODEL\n",
    "##########################\n",
    "\n",
    "\n",
    "class ConvolutionalAutoencoder(torch.nn.Module):\n",
    "\n",
    "    def __init__(self):\n",
    "        super(ConvolutionalAutoencoder, self).__init__()\n",
    "        \n",
    "        # calculate same padding:\n",
    "        # (w - k + 2*p)/s + 1 = o\n",
    "        # => p = (s(o-1) - w + k)/2\n",
    "        \n",
    "        ### ENCODER\n",
    "        \n",
    "        # 28x28x1 => 28x28x4\n",
    "        self.conv_1 = torch.nn.Conv2d(in_channels=1,\n",
    "                                      out_channels=4,\n",
    "                                      kernel_size=(3, 3),\n",
    "                                      stride=(1, 1),\n",
    "                                      # (1(28-1) - 28 + 3) / 2 = 1\n",
    "                                      padding=1)\n",
    "        # 28x28x4 => 14x14x4\n",
    "        self.pool_1 = torch.nn.MaxPool2d(kernel_size=(2, 2),\n",
    "                                         stride=(2, 2),\n",
    "                                         # (2(14-1) - 28 + 2) / 2 = 0\n",
    "                                         padding=0)                                       \n",
    "        # 14x14x4 => 14x14x8\n",
    "        self.conv_2 = torch.nn.Conv2d(in_channels=4,\n",
    "                                      out_channels=8,\n",
    "                                      kernel_size=(3, 3),\n",
    "                                      stride=(1, 1),\n",
    "                                      # (1(14-1) - 14 + 3) / 2 = 1\n",
    "                                      padding=1)                 \n",
    "        # 14x14x8 => 7x7x8                             \n",
    "        self.pool_2 = torch.nn.MaxPool2d(kernel_size=(2, 2),\n",
    "                                         stride=(2, 2),\n",
    "                                         # (2(7-1) - 14 + 2) / 2 = 0\n",
    "                                         padding=0)\n",
    "        \n",
    "        ### DECODER\n",
    "                                         \n",
    "        # 7x7x8 => 14x14x8               \n",
    "        # Unpool\n",
    "\n",
    "        # 14x14x8 => 14x14x8\n",
    "        self.conv_3 = torch.nn.Conv2d(in_channels=8,\n",
    "                                      out_channels=4,\n",
    "                                      kernel_size=(3, 3),\n",
    "                                      stride=(1, 1),\n",
    "                                      # (1(14-1) - 14 + 3) / 2 = 1\n",
    "                                      padding=1)\n",
    "        # 14x14x4 => 28x28x4                            \n",
    "        # Unpool\n",
    "        \n",
    "        # 28x28x4 => 28x28x1\n",
    "        self.conv_4 = torch.nn.Conv2d(in_channels=4,\n",
    "                                      out_channels=1,\n",
    "                                      kernel_size=(3, 3),\n",
    "                                      stride=(1, 1),\n",
    "                                      # (1(28-1) - 28 + 3) / 2 = 1\n",
    "                                      padding=1)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        \n",
    "        ### ENCODER\n",
    "        x = self.conv_1(x)\n",
    "        x = F.leaky_relu(x)\n",
    "        x = self.pool_1(x)\n",
    "        x = self.conv_2(x)\n",
    "        x = F.leaky_relu(x)\n",
    "        x = self.pool_2(x)\n",
    "        \n",
    "        ### DECODER\n",
    "        x = F.interpolate(x, scale_factor=2, mode='nearest')\n",
    "        x = self.conv_3(x)\n",
    "        x = F.leaky_relu(x)\n",
    "        x = F.interpolate(x, scale_factor=2, mode='nearest')\n",
    "        x = self.conv_4(x)\n",
    "        logits = F.leaky_relu(x)\n",
    "        probas = torch.sigmoid(logits)\n",
    "        return logits, probas\n",
    "\n",
    "    \n",
    "torch.manual_seed(random_seed)\n",
    "model = ConvolutionalAutoencoder()\n",
    "model = model.to(device)\n",
    "\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 001/010 | Batch 000/469 | Cost: 0.7008\n",
      "Epoch: 001/010 | Batch 050/469 | Cost: 0.1782\n",
      "Epoch: 001/010 | Batch 100/469 | Cost: 0.1394\n",
      "Epoch: 001/010 | Batch 150/469 | Cost: 0.1240\n",
      "Epoch: 001/010 | Batch 200/469 | Cost: 0.1173\n",
      "Epoch: 001/010 | Batch 250/469 | Cost: 0.1105\n",
      "Epoch: 001/010 | Batch 300/469 | Cost: 0.1045\n",
      "Epoch: 001/010 | Batch 350/469 | Cost: 0.1060\n",
      "Epoch: 001/010 | Batch 400/469 | Cost: 0.1051\n",
      "Epoch: 001/010 | Batch 450/469 | Cost: 0.1002\n",
      "Time elapsed: 0.12 min\n",
      "Epoch: 002/010 | Batch 000/469 | Cost: 0.0998\n",
      "Epoch: 002/010 | Batch 050/469 | Cost: 0.1023\n",
      "Epoch: 002/010 | Batch 100/469 | Cost: 0.1018\n",
      "Epoch: 002/010 | Batch 150/469 | Cost: 0.0967\n",
      "Epoch: 002/010 | Batch 200/469 | Cost: 0.0982\n",
      "Epoch: 002/010 | Batch 250/469 | Cost: 0.0985\n",
      "Epoch: 002/010 | Batch 300/469 | Cost: 0.0961\n",
      "Epoch: 002/010 | Batch 350/469 | Cost: 0.0998\n",
      "Epoch: 002/010 | Batch 400/469 | Cost: 0.0975\n",
      "Epoch: 002/010 | Batch 450/469 | Cost: 0.0967\n",
      "Time elapsed: 0.24 min\n",
      "Epoch: 003/010 | Batch 000/469 | Cost: 0.0953\n",
      "Epoch: 003/010 | Batch 050/469 | Cost: 0.0921\n",
      "Epoch: 003/010 | Batch 100/469 | Cost: 0.0955\n",
      "Epoch: 003/010 | Batch 150/469 | Cost: 0.0960\n",
      "Epoch: 003/010 | Batch 200/469 | Cost: 0.0959\n",
      "Epoch: 003/010 | Batch 250/469 | Cost: 0.0929\n",
      "Epoch: 003/010 | Batch 300/469 | Cost: 0.0952\n",
      "Epoch: 003/010 | Batch 350/469 | Cost: 0.0935\n",
      "Epoch: 003/010 | Batch 400/469 | Cost: 0.0939\n",
      "Epoch: 003/010 | Batch 450/469 | Cost: 0.0946\n",
      "Time elapsed: 0.36 min\n",
      "Epoch: 004/010 | Batch 000/469 | Cost: 0.0931\n",
      "Epoch: 004/010 | Batch 050/469 | Cost: 0.0965\n",
      "Epoch: 004/010 | Batch 100/469 | Cost: 0.0900\n",
      "Epoch: 004/010 | Batch 150/469 | Cost: 0.0883\n",
      "Epoch: 004/010 | Batch 200/469 | Cost: 0.0957\n",
      "Epoch: 004/010 | Batch 250/469 | Cost: 0.0956\n",
      "Epoch: 004/010 | Batch 300/469 | Cost: 0.0885\n",
      "Epoch: 004/010 | Batch 350/469 | Cost: 0.0901\n",
      "Epoch: 004/010 | Batch 400/469 | Cost: 0.0943\n",
      "Epoch: 004/010 | Batch 450/469 | Cost: 0.0933\n",
      "Time elapsed: 0.48 min\n",
      "Epoch: 005/010 | Batch 000/469 | Cost: 0.0885\n",
      "Epoch: 005/010 | Batch 050/469 | Cost: 0.0953\n",
      "Epoch: 005/010 | Batch 100/469 | Cost: 0.0896\n",
      "Epoch: 005/010 | Batch 150/469 | Cost: 0.0944\n",
      "Epoch: 005/010 | Batch 200/469 | Cost: 0.0935\n",
      "Epoch: 005/010 | Batch 250/469 | Cost: 0.0914\n",
      "Epoch: 005/010 | Batch 300/469 | Cost: 0.0898\n",
      "Epoch: 005/010 | Batch 350/469 | Cost: 0.0883\n",
      "Epoch: 005/010 | Batch 400/469 | Cost: 0.0906\n",
      "Epoch: 005/010 | Batch 450/469 | Cost: 0.0924\n",
      "Time elapsed: 0.60 min\n",
      "Epoch: 006/010 | Batch 000/469 | Cost: 0.0862\n",
      "Epoch: 006/010 | Batch 050/469 | Cost: 0.0881\n",
      "Epoch: 006/010 | Batch 100/469 | Cost: 0.0879\n",
      "Epoch: 006/010 | Batch 150/469 | Cost: 0.0847\n",
      "Epoch: 006/010 | Batch 200/469 | Cost: 0.0922\n",
      "Epoch: 006/010 | Batch 250/469 | Cost: 0.0871\n",
      "Epoch: 006/010 | Batch 300/469 | Cost: 0.0893\n",
      "Epoch: 006/010 | Batch 350/469 | Cost: 0.0900\n",
      "Epoch: 006/010 | Batch 400/469 | Cost: 0.0843\n",
      "Epoch: 006/010 | Batch 450/469 | Cost: 0.0856\n",
      "Time elapsed: 0.72 min\n",
      "Epoch: 007/010 | Batch 000/469 | Cost: 0.0898\n",
      "Epoch: 007/010 | Batch 050/469 | Cost: 0.0840\n",
      "Epoch: 007/010 | Batch 100/469 | Cost: 0.0898\n",
      "Epoch: 007/010 | Batch 150/469 | Cost: 0.0928\n",
      "Epoch: 007/010 | Batch 200/469 | Cost: 0.0920\n",
      "Epoch: 007/010 | Batch 250/469 | Cost: 0.0864\n",
      "Epoch: 007/010 | Batch 300/469 | Cost: 0.0860\n",
      "Epoch: 007/010 | Batch 350/469 | Cost: 0.0839\n",
      "Epoch: 007/010 | Batch 400/469 | Cost: 0.0856\n",
      "Epoch: 007/010 | Batch 450/469 | Cost: 0.0843\n",
      "Time elapsed: 0.84 min\n",
      "Epoch: 008/010 | Batch 000/469 | Cost: 0.0817\n",
      "Epoch: 008/010 | Batch 050/469 | Cost: 0.0858\n",
      "Epoch: 008/010 | Batch 100/469 | Cost: 0.0894\n",
      "Epoch: 008/010 | Batch 150/469 | Cost: 0.0897\n",
      "Epoch: 008/010 | Batch 200/469 | Cost: 0.0815\n",
      "Epoch: 008/010 | Batch 250/469 | Cost: 0.0892\n",
      "Epoch: 008/010 | Batch 300/469 | Cost: 0.0859\n",
      "Epoch: 008/010 | Batch 350/469 | Cost: 0.0883\n",
      "Epoch: 008/010 | Batch 400/469 | Cost: 0.0834\n",
      "Epoch: 008/010 | Batch 450/469 | Cost: 0.0940\n",
      "Time elapsed: 0.95 min\n",
      "Epoch: 009/010 | Batch 000/469 | Cost: 0.0888\n",
      "Epoch: 009/010 | Batch 050/469 | Cost: 0.0856\n",
      "Epoch: 009/010 | Batch 100/469 | Cost: 0.0881\n",
      "Epoch: 009/010 | Batch 150/469 | Cost: 0.0853\n",
      "Epoch: 009/010 | Batch 200/469 | Cost: 0.0868\n",
      "Epoch: 009/010 | Batch 250/469 | Cost: 0.0795\n",
      "Epoch: 009/010 | Batch 300/469 | Cost: 0.0868\n",
      "Epoch: 009/010 | Batch 350/469 | Cost: 0.0899\n",
      "Epoch: 009/010 | Batch 400/469 | Cost: 0.0882\n",
      "Epoch: 009/010 | Batch 450/469 | Cost: 0.0950\n",
      "Time elapsed: 1.07 min\n",
      "Epoch: 010/010 | Batch 000/469 | Cost: 0.0854\n",
      "Epoch: 010/010 | Batch 050/469 | Cost: 0.0839\n",
      "Epoch: 010/010 | Batch 100/469 | Cost: 0.0873\n",
      "Epoch: 010/010 | Batch 150/469 | Cost: 0.0871\n",
      "Epoch: 010/010 | Batch 200/469 | Cost: 0.0851\n",
      "Epoch: 010/010 | Batch 250/469 | Cost: 0.0831\n",
      "Epoch: 010/010 | Batch 300/469 | Cost: 0.0843\n",
      "Epoch: 010/010 | Batch 350/469 | Cost: 0.0860\n",
      "Epoch: 010/010 | Batch 400/469 | Cost: 0.0840\n",
      "Epoch: 010/010 | Batch 450/469 | Cost: 0.0818\n",
      "Time elapsed: 1.19 min\n",
      "Total Training Time: 1.19 min\n"
     ]
    }
   ],
   "source": [
    "start_time = time.time()\n",
    "for epoch in range(num_epochs):\n",
    "    for batch_idx, (features, targets) in enumerate(train_loader):\n",
    "        \n",
    "        # don't need labels, only the images (features)\n",
    "        features = features.to(device)\n",
    "\n",
    "        ### FORWARD AND BACK PROP\n",
    "        logits, decoded = model(features)\n",
    "        cost = F.binary_cross_entropy_with_logits(logits, features)\n",
    "        optimizer.zero_grad()\n",
    "        \n",
    "        cost.backward()\n",
    "        \n",
    "        ### UPDATE MODEL PARAMETERS\n",
    "        optimizer.step()\n",
    "        \n",
    "        ### LOGGING\n",
    "        if not batch_idx % 50:\n",
    "            print ('Epoch: %03d/%03d | Batch %03d/%03d | Cost: %.4f' \n",
    "                   %(epoch+1, num_epochs, batch_idx, \n",
    "                     len(train_loader), cost))\n",
    "            \n",
    "    print('Time elapsed: %.2f min' % ((time.time() - start_time)/60))\n",
    "    \n",
    "print('Total Training Time: %.2f min' % ((time.time() - start_time)/60))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAABIAAAACqCAYAAADV0DQZAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvOIA7rQAAIABJREFUeJzt3XW8HNX5x/HP/IoXL67B3QMUdwsUKa7BITgtEiC406JFg0Px4pRgwSWQpDhBS/DgFCm0pZ3fH8l3ztm9e29m712d+32/XnndvbOzd2efzJyZnfOc5yRpmmJmZmZmZmZmZsX1f83eADMzMzMzMzMzqy/fADIzMzMzMzMzKzjfADIzMzMzMzMzKzjfADIzMzMzMzMzKzjfADIzMzMzMzMzKzjfADIzMzMzMzMzKzjfADIzMzMzMzMzK7ge3QBKkmT9JEneSJLk7SRJBtZqo8zMzMzMzMzMrHaSNE2798Ik+QXwJrAO8CEwHNg2TdPXard5ZmZmZmZmZmbWUxP04LXLAW+nafp3gCRJbgQ2ATq9ATTddNOlffr06cFbtqeRI0d+kabp9NW8xrHKz7GqTm+M1+jRo/niiy+Sal/XG2MFPg6r4Vjl51jl5/Y9P7fv1fFxmJ9jlZ/brPzcZlXHx2F+eWPVkxtAswIfRL9/CCxfvlKSJHsCewLMMcccjBgxogdv2Z6SJHkv53qOlWOVW95YjVu3V8erb9++udft7bECH4fVcKzyc6zyc/uen9v36vg4zM+xys9tVn5us6rj4zC/vLGqexHoNE0Hp2naN03TvtNPX/WN4V7FscrPsaqO45WfY5WfY5WfY5WfY1Udxys/xyo/xyo/x6o6jld+jlV+jlV+PbkB9BEwe/T7bOOWmZmZmZmZmZlZC+nJDaDhwHxJksyVJMlEwDbAXbXZLDMzMzMzMzMzq5Vu1wBK0/TnJEn2A+4HfgFckabpqzXbMjMzMzMzMzMzq4meFIEmTdN7gXtrtC1mZmZmZmZmZlYHPboBZGZmZmZmVk+ffPIJAHfeeWe27KyzzgJgiSWWAOCWW25p/IaZmbWZus8CZmZmZmZmZmZmzeUMIBuvu+++O3u8ySabALDRRhsBcNddrvtt+X333XcADBo0CIDzzjsve26mmWYCYJdddgFgxx13BGD++ecH4Be/+EXDttPMep/7778fgL/97W8APProowA88MADHdZdZJFFADjqqKMA2HbbbRuwhWa9zx133AHAoYceCsDbb7+dPbf66qsD0L9//4Zvl5kZwCuvvALAPvvsA8ATTzwx3te8/PLLACy66KL127AuOAPIzMzMzMzMzKzgnAFk43XTTTdlj5MkAWDeeedt1uY03NChQ7PHJ5xwAgCPP/54yTrKiIqzpSxQD7p6y0eOHAmE/Qng008/BeC0004r+XnccccBcOSRR2brTjBB72u6FAeAE088seS5//73vw3eGrP29tVXXwFw/fXXZ8v+8pe/AKF9T9MUKG2n5LXXXgNC5oGOyXvuuQeAueeeux6bXRgnnXQSAMcffzwA//nPf5q5OT1y5ZVXArD33nsDsNZaawGl1w7VUC/y2WefXYOta1/nnHMOAAMHDgTgX//6FxCyfiBkoU8xxRSN3bg2oXYKQtaiYnX66acDMGDAgMZvWAOoTVFbA+EavjseeughIBzf1jtpJMPgwYOzZTqWfvWrXwFw8sknA7DFFlsA8H//F/JtNKpB+5MzgMzMzMzMzMzMrC56Xze65fbzzz8DlXvmDj744EZvTtPst99+2ePXX38dgGWXXRaAn376CQgZGJdddlm27u67796oTWxZ33zzDQDHHnssACNGjABCj/qMM86YrTvxxBMD8P7775f8DWW+xL3wRxxxBNC7MoHiz9/u9ZDefPNNAC688EIAfvzxx+y5Dz74AAi9JBNOOCFQ2ks588wzAzDppJPWf2Pb0M0335w9Vi/6M888A8AhhxwChDYs3q9mm202AFZYYYWGbGcz3XfffQAccMAB4113wQUXBGD55ZfPlr333ntAqBOkc8OGG24IhEwggHnmmafnG1wQipfOCe3elgEstNBCQMjcueqqqwD497//3a2/d8MNNwCw2GKLAbDrrrv2cAvbyzvvvAOE87wyf1RnK77OmmyyyRq8de3ljTfeyB6rrf/++++BkJld1AwgZdDFGdOVsjnz2mGHHQC4/PLLAejXr18Ptq79qDaeRjzEWS2XXnopABtssEHjN6zBzj33XCDsBxBmH1xttdUqvkZZQzFd4zaLM4DMzMzMzMzMzAqu13SfaxysekOfffbZ7LmXXnoJgLXXXhuA7bffHoCdd965gVvYeoYNGwaEugi9jSq0x7143377LQCTTz45AP/73/8AePrppwE49dRTs3WdAQS//e1vgdLjLRbHSzPM7bXXXkDooVdvlXqMATbeeGMAllhiiRpvcevR7AKPPfZYk7ekdpTlE88CV07//3LmmWdmj3/9618DsO666wKhh/zrr78G4IsvvsjWfeGFF4DQexdnnRXNWWedBYQsHwgZFur51DlQWYtxBsYss8wChLpvinORPPnkkwD86U9/yv0aXRvE+6t69NRe3XjjjUDocdfYf4Dnn3++B1tcDMooVgx17ixCBpCOE/1ccsklge5fQ6oe3h577AGEa1Qdu0WnDEZlWOs8r5obzvrJ78477+z0uammmqqBW9J4yqSrRrxv6ZypDDQdl3vuuSdQmmm74oordns7W5Uy9pWprRqnur6Ks6l07VBEun+gc5dqacWzEY7vPKbrBIC+ffsC4bqiWZwBZGZmZmZmZmZWcL4BZGZmZmZmZmZWcIUdAqY079/85jdAGLqjtONKNCWbXqupYOMikUrtLTIVtzzwwAObvCXNpSE3cZpe+VSjSvtbfPHFgdIpN7/88ksgTAtYdJ9//jkQho9AKDybxzTTTAOEtNr9998fgKuvvhoIQ8EANt10UyAUMZxvvvm6u9ktb9SoUUBolywMT9XPSy65BAjFpNXex7RfKY15hhlmqPt21puOLxUg1BARTV8OYaiXlnX2O4QC7LfeeitQzCFgmna8s2GpsT59+gCVizXqXHDxxReXLNdQMB23EP5/ttxyy+o3uCAeeeQRAG6//faS5ZtttlkzNqeuFl54YSBf8Wadwz788MMOz+l6VcMVdY2qYcFFov0D4JRTTil5TsMpNfTe8qs0DErDoONh1UWi4+Mf//jHeNedY445gDDM8Iorrsie07X7aaedBoQJST7++GMgHLsQJgho9yGtP/zwQ/b4qKOOAuDBBx8c7+t0jhs+fDgQJu9QW6hJJ9qRis4PGTIEgMMPPxzI93990UUXAaXnPcVqookmqul2VssZQGZmZmZmZmZmBTfeDKAkSa4ANgI+S9N00XHLpgVuAvoAo4Gt0jT9un6bmc+OO+6YPVYWQvkU5pru8JhjjsmWTT311EAooKm7nldeeSUQMhsg9B4X2b333guE4qm9naZi7YqK6cWZKOqt23rrreuzYS1GUyIeeeSRHZ6bd955Adhuu+0AOP300wGYddZZO/176vWca665gNLCtspSU3bQSSed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GNP1lNoz9RTr91lmmSVb96KLLgJghRVWAEKGZ2+hNuuqq64CYNCgQSXLp5566mzdbbbZBoB55523gVvYPuLr97POOguA448/Hghx1c+iZa3//PPPAPz5z3/Olu23335AyMBTZob2Lb0GOl57KH5rrbUWAAsttFD2XG86j+paIs4kUxaZMviL7NNPPwXgySefzJatvfbaQNgP9B1H+9X222+fravzxc033wyEa4m4XWsEX1mbmZmZmZmZmRVcr80AqtTDpzt3ugvem+7oVqKeA/XUQcjKWGyxxQCYbrrpGr9hDfLqq68C8O6772bL1KOnO7Uau6nMi7jH76OPPgJg7rnnrv/GtqhnnnkGgKuvvhoId8V1bMXHmHoO1FuuO+f6OXjw4Gzdgw46CAiZV73B22+/nT0un42v3bJk9H96++23A6WzK6nd0WdbbbXVAPjlL3+ZraPjrbe30eW++OILoLTXV73ojzzyCAAbb7wxEPaZTTfdNFtXy2aeeWagmHVstK+ptp2yLWD8mT9xPMqzP9W26W+cf/752bqHH3440H7HaT3oemLnnXcGwvWWeuah/TKAtL1qj3T8xe2aMn7iGiPx7x9++GG2bOuttwbg8ssvB8K5sfzYLaprrrkGgH333RcI5wtla/ztb3/L1p1rrrkavHXt5c0338weH3vssUDY59TOFy3zR8477zwATjjhhGyZsldEmXkSZyCXH6uqR3nDDTcApeeD+eefvwZb3JoUB9Xiuuuuu4DS70Y636kGXhEp40mZP+uvv3723HzzzQeEtlmZZGrX4xEMw4YNA8IokWad74p9FjEzMzMzMzMzs+JmAOnu2+jRowH46quvABg1ahQQxulDx0yDVVddFYCVVloJ6L01XDRj04svvpgtU1x7w6xDuusd9wpr7O+KK64IwPTTT1/ymi+//DJ7HGcD9SZxr4l6V9SjJzrW4qyO2WabDQi9Uur11N32ZZddNltXNRYOPvhgINTfKCL1IleanaJdqWbRTjvtBJT2wunzqrd3jjnmAOB3v/tdto56TJZcckkgtEd6TdyjqeNQ7zHTTDOVrFsEH3/8MRBmwolrkX399ddAaMdUs0bH19lnn52tu9122wGw7rrrAuFcWKSMg3vvvRcI9Ry++eab8b5GPb033nhjtkznPmWK6pi87rrrgNLewc8++wwI+15vpN5T7Ydqz4pwfaV6Wspq0nWS9jHoWIOrPNssbtOVQaRMoOOOO67kfXR8QnGOzTgeaqvVdqutUq0aZ/3kVykLTYqa+aNsae1T5Vk/MbXtilM8G6Ge0+y/avOVWaSacVDMDCC1Y7fccgsAp5xyCgDff/89UHoNpX1LsY6v7dud6kXdc889AHz77bdAaZtVni2tdllt9qyzzpo999vf/haANdZYA2je9+hinDnMzMzMzMzMzKxTvgFkZmZmZmZmZlZwhRgCpnTR119/PVt25513AiEtTamkKoQZF5FT6q3SuZS69eCDDwKwyCKLZOvOOOOMtf8ALUZT0in9uFJhTBXqLeIQMKU9DhkyBChNc9SwQKXy9+nTBwhTCsdDwEaOHAmUThVZZBpe+cknn2TL1ltvPSAch4qlivjGxfnmmWceIAyXUPFCFQr+4x//mK2rfVL73zHHHFPLj9JStG9puFQl7TKcSfuBioN3lZqt/2NNuTlw4MDsObXZGhqgv1NpKleldmuoif6O9pl2bsN0nnv//feBUKQy/kxKxdbnV1x1ToyHQ5966qkALLzwwkAYttNuRXkrUdr6E088AYShcZUobVuFLTX0V8NUIcR4mWWWAeDaa68Fwv4WF5fWVNVq99rleO0pnUMhnBd22223knVOOukkoONw6naifUH7h66d4imR1fZp/9AwL5UniIv1/v3vfwdCAVENAVNRaL0G2n8iDrXZL7/8crZMbbSOkzPOOAMIRaFt/DS0ddttt+3wnNo3DfktCp3TdM14xBFHAJWHzOt7nq5R+/btC5QOL1xggQWAMKHLDjvsAMArr7wCwH333Zetu9RSSwGNn8q7VvQ9Oj4vXnzxxUBof8pNOumk2WMNPy8vLK64xue8drvm0neSN954Awjbn+c8rkki4iGCKlnQ7OsqZwCZmZmZmZmZmRXceDOAkiS5AtgI+CxN00XHLZsWuAnoA4wGtkrTtPPutDrT1Le33XZbtkzT9GkK4X79+gGhkOzxxx+fravelvLihJtssglQ2otV5Ayg8kyoropjtnNv3fio4Jd66uK71eo10N1tZa0oHnHPXPl05u121zsvFURTVocKM0NpJgbA3HPPDYSeJx1jEIrvab/THXPFT70uMU0Nr+KQRSmICaEdUtaippmOqQeiXfYtbee8885b8ntMx9bss88OhKyU2AwzzFDyu7LttK8cdthhHV6j+Cnr7NBDDwVKiz62g7hdVlbFgQceWLJOnHkQF4QGuPrqq4HQm6X2Lrb//vsDoUhhs3uqakH7RnlB+rh4o7J4dFxtuummQNfFUrWf7rPPPkA4b8TZjcogUqbWtNNO281P0XxxhqeKoyqzSee8MWPGAPDOO+9k6yrTp9ycc84JdCyi2c6UORAfl+XnwvLfY9pX9Xd0XaFpg9dcc81s3XbPANJ5bvjw4dkyZXSqkPpGG20EFGsfqRfFU1N1v/XWWx3W0XVr0eizX3HFFUDX1xfalzTRgdr87bffPltX15Mq9K8C/7p+VTFoCNcRuvaIs2NamdoajYqJM8Y0iVJn4slXdF2i7EfFc9dddwVgnXXWydZV1my7TAAwyyyzALDjjjsCYT+I6fPr/K+fOucriwha5zthnm9LVwHrly0bCAxN03Q+YOi4383MzMzMzMzMrAWN93Z6mqaPJ0nSp2zxJsDq4x5fDTwKHF7D7cpF4z0vu+wyAG666absOWVkqOdg6aWXBsJYTk3DBuHOp6aHVc+Mxu511VNTJKqRcMEFF3S6jqY9XGKJJYDm38GsB/Ui6P89Hj+sx4suuigQpvZTnY14ml/tP0WlqTZVM6rS+Hz1juv422WXXYAw7lpj0WOKm2Kr388999xsHd1t33vvvYH2nxa9EtUg66rugcZdt8uUm2ov1D4vv/zyQOhhg9ArdPrppwNhbH18PKnXWz10v/71r4FwzMZTcKvnSbWE1PPXbm2X9nEddxD2DT2n+gPxmH0dRzpmlNWiXlLVGYHQO7ryyisDpT187U7thzLBtB/EvXkbb7wxULld6oz2QY3rn2+++UqWQ+gNVL2ldsoA0r6lc398PaRjSJ9Vx6U+bxxbZYqqZ1gZQYsvvnjJ8iJQPHraLuscoB5z/V+0S3ufh66/hw4dmi1T9oRquHja9/xUC/XMM8/s8JyOsbPPPhtonyyVvP7yl78AYVRHpUyLOeaYA4C99toLCNk88TTd5VQHVplpOi9q9AnAX//615K/p/NAq1JGva4LDjnkEAA+/vjjDuuqPdNn0vW8MhQhfH/UcazzhDJhzznnnGxdZdoq5rrGa1U6btQOKWtM+1tXlJ0e71+qk6R2vVnfFbs7XmLGNE2VBzwG6HRcVJIkeyZJMiJJkhEqZmqVOVb5OVbVcbzyc6zyc6zyc6zyc6yq43jl51jl51jl51hVx/HKz7HKz7HKr8cDatM0TZMk6bT7PU3TwcBggL59+9akm14ZGrfeeisAp5xySod11AOlO8C6q6m74/E49mHDhgEdZ7tSHYVG9RTXI1bV0J1bjR2WuDdUMWl2pft6xkoZCLrrrf0tfqz9Sw2MZvBQTYT477SCWsUrzrRRnQf1pOi5Y489NltHvQo6hqqpt1LeuxzXfSmfwUf/H13V6sir2cehdFWLSxke6667biM3qYPuxkq9/gcddBBQ2vaWZ3XpmIr/b8trQZT3ZC644ILZ4xdeeAEIdYe0rzS6XlRP9yvFQ72bUNo2QeUsCsVP+9HDDz8MhIyMOA7qtWv2zIW1ipXO7RCyUVTzSPXDVIMGqsv8Kaf2Sj+1n0GIa/n/V63Us8164IEHgI7XUACrr746ED5zeSZmXBNPn13ZK9rvNJNmo7RK+55H+bVno68pGhErHSeqSQYda8S1Q4Zvq+xXiqdmZY33oS222AIIx20z1SpemqkSwnWEZrSK3it7vOeeewIhi6erzB9RDJXxosxlZf1AmMWuHm18rWIVf+/VdYBqFmm/ibdf9QLVVm+wwQYlfy+uH6jXaV2dX3WdoZ8Am222GQCXXHIJAEsuuSRQm+/a9TwO9dl0fRRfk2q/Ut0kXaNq34vr5vbv3x8Is0a3WwbQp0mSzAww7udn41nfzMzMzMzMzMyapLs3gO4C+o973B+4szabY2ZmZmZmZmZmtZZnGvgbGFvwebokST4EjgVOA25OkmQ34D1gq3puZDkNPdltt92AUAw6pmKLf/zjHwFYe+21gVDoWYXnoPMiz3/4wx+AUBCzqBS/q666CuiYbvvYY49lj5dbbrmGbVezaBiApoOMC1kqTfKhhx4qeU2lYnOaOrDdis1WomNEacUQpjZW6qem646nga9FyroKr8XTU2oIlAqTHn54w2vQ143SajVdtfYtFS+EMEyqFkPemkH7xZZbbgmEqe4hTCmtgv36/69mqEg8FEqFabXPDBo0CGiPYQUxHWcqtliJYvfSSy9ly7TfaAjY008/DYS2LG6f9P+iaYLbrUiojh0VQI0LoWoonIpfb7755kCYurxWNFS10v6lIQMqetnoYYjV0LXTs88+C8CTTz4JhCG/EPYd7Xc65ym1XcdwvO78888PwCqrrAIUc2pv/d/H+4CGp5RPQ93V6+++++6SdRXnIsRM8bj//vuB0qHOM844tqyohh+qPdc+pP0spmE/WkcTUPQWiudtt91W8nvchh955JFAMQqua3ipJsKA8J1QtC/EZUI0/EjtUDU0bGynnXYCwvEJYXIBFS5XgeBWaOMff/xxAJ5//vlsma7T1dZoAht97wX4zW9+A8BUU0013vfQ39lqq7G3A1TuQd8r43OB9s0BAwYAYWKKdpkcQe1TvL36nDpP6jNqv3j33XezdZ966ikA1lxzTSAUhVaJlUbJMwvYtp08tVaNt8XMzMzMzMzMzOqgLbsRdKdRd9YqUW+pprr77LOxZYo++OADoOup3dXTrLuf7drLXq0hQ4aU/K6ezL59+zZjc5pu7rnnBuD3v/99tkx39cun9VXvVbOLY9eLCu0pgw46FtpTr3a9epf++c9/dlim7I4i9IjKE088AYRi7OrF6tevX7ZOEbLKIPSOaVpRgF133bVknfKCoNUqL8Crnpm4SG870HlImXcQeu10flOWS9zb9NZbbwGhyKrioPNofOwo+1GFkVuh97Iamhji+OOPB0rP88puUhxrnfkjXe2n5YXtW82LL76YPdb1lbIwVlhhBaB025WJoZ5QFedX73x8LtDjuBgohIy2eGrzVo3P+Og8qbYlnhRCn1u96TrW9Ht8nalC25deemnJ3+9O1kKr0v+7ivTHEwEojtoH77nnnpLXxtchr732Wsnf01TVKsrbSpNx1JMyD0466aSS5ZrCHIr1XUb7iDJ7K1GbE3/unhxDapf0M27rdT598MEHS35vJp3rNV29Cj5D2D7F6IILLgBKM63zZP6IYrHYYosB4dyg71Hx9dYuu+wChGNeWVzKrIL2uKaPz28aHaPvy9pHFOfrr78+W1fXJSrErWNUGZ6N0p5nWTMzMzMzMzMzy631b7FVoJ4T3dlXDZv4bpzG1OnOmnoSdCc07hnUY73+vvvuA9qv/kF33XLLLUDHjA71urRrb1xP6Y523DOpaTRVA0g1aLRfDRw4MFu3CPWSVFPjT3/6E1A6xbuoRtI666wD1H5/Uc/eOeec0+G5iy66CChGRoz2oWeeeQYIvSP6bBp3DMVrm6aZZprs8XrrrQfAp59+CoRx9931ww8/AB3rRLVCD101KtW2uPLKK4FQS0kZGPFYf53f1KOmfUftvcbqQ6gXpN67dmn7dQ3w9ttvA5UzfG+44QYgZPbWS1fj+DfccMO6vnd3nX/++UBpHZajjz4aCJ9n4YUXBkrrryg7TxloyjK78cYbgdIsbf0dZaep7oPqcyjDCGDBBRfs+Yeqk88//xwonS5ZUx7rfKnPGmdUjRw5suR1Ov622WYboLT3V9Mvf/zxxyV/T3WTVEMCYKONNur5h2oiZSiq7YKwH+pafOONNwbC/qWplqHjdes777wDhOuGomcA6TpB+1d527fHHntkj5WN0c503lYWmK6bYuW1ojTtdq0oyyPOLFLcVQuzFc6dqsWmTMK4zdL3aF2/r7vuukDPpyRXW6Xv4Ouvvz4QaslB+P/R/2V5xl8ttqPR9JnKs+y0fIcddsiW6bv1zTffDIRsxdVXX73em1mi+XuomZmZmZmZmZnVVVtmAKnOimY6UW9AV1kAmvlEPSpxr4F6QNXroto3RRbXU4nHXkO4c12k8cK1Un6XV3FUDOO71u0804LuzKsnt3xcOYSq95oxqF49HtqW8p4+aO8Yl1PvzO23317yu3RV86xIlllmGSCMGe9pO6QMoHLl8W0X8XGmrAn1/t55551AaXaTsoRE50vFN55FU8dYnG3WTjTDVyWNmnWqq8yyVugVjunYUH2aY445psM62ieGDx8OwEILLZQ9pyw9XVedffbZQDgfxnWWdO7UT/XcK9MorpHQihlAmqlQs6Jecskl2XNqm5UtrJnS4tpmyk7TLLb6/Mq+6mq/UXbBsGHDgNK6jOUZfu1CMVMdxfj8riwCZf4om1o/4zpxaseVaaDfVRtopZVWytZtteOvFsZXE3WJJZbIHhfheknX3Dp/xZ+7/DugMn9q/f+u96lUR1DXK62Qla6sG41YiLdX36O1nbWegUrvpeyeOGuvvK1THae4BlCRzTrrrEBoo5p1LVq81tDMzMzMzMzMzEq0V5fBOLrDqmru+hnfVdQdNY0HVu/VXHPNBZT2TKmuiWpPFOEueWd0t1xjD6HjrDs77rgjUNpzYGOVzwKgu9zK/In3nbh2ULtRr9wZZ5wBhB5dZf0AbLvttkDtew7KnXfeeR2WqVbJGmusUdf3rrc4+0L1jOK6ERBqGGy++ebZsnrNYNQKdAzVqh0uzxxTj5fawp7WGGomzdKhn/vvvz8QarJAyBbQ7HI6nipl1Em7ZUcpq+KTTz4pWX7ttddmjzVTVb3o+kMze8Q0W2GcPdMK1M6fcMIJQOUsFNWMUg0gzTYEIVtR9Vb0eh1ThxxySLaurtt0ztTPSjOf6O+0Qi/6qFGjgFCrJz62RDXMlPmj+hpxPRbVD1TmumZ5LJ+VrxLF6rnnngNKa/Hpmla1waqZuaeZdO5TOxx/fu1zyy+/PBCyQrUPxVmhyj5TvU9lVtU7M7lVKLOiPHtP7fySSy6ZLStCLDTD4IknntjhOe0Xan/UZqn9rZWuZvrScd5M2i7tG5Vq4ilWmilb9dxUuwdC21xeq0e/6/t1vEx1h9Suqc1SbaRKNAtYXAuyyHT9rppxOn82Wvu3BmZmZmZmZmZm1iXfADIzMzMzMzMzK7i2HALWmThdWOlnmk5Sv2sK0rg4n9Iiizz0S5R2GxcwLKeUYutI8fv73/8OhGFeSjmNh0O1Qvp6d+l4+fDDD4EwDCk+Ruo99OvNN98E4OSTT+7w3NZbbw20b4yVLmZmEuYAABOKSURBVBsPxVQhPFF8jzrqKACWXXbZxmxcAcSp2S+88ELFdZSqXKSi/xp6oiGSENK4NSRCQww1FWlM+1xXw1Fakba3UlFOqffwBw3tqVQwf8UVVwRar73SsKEhQ4YApf/vSvfXNqtQdEztl16nGGvoVzwEbHzFRlstNqJ9qnzoV7w/LbbYYkDH4rCV9scFFlgACNcOGgYQx15/u3z4s/YtFZSGcF7Q/2GjpxLuLl1jVBpuqjZL8VS7Vl5IHMJwsS233LLkuXgoS5F1NoRQRcF17VYUGs6ka/F4X9BjlWTQELBaUdH8Sse1jtlWaMfKh2xVUj4VuyaQiL8bazKIV199FQjfnyW+ttK6Ou5UNkKlDeJyB3LggQcCYYKG3kJx0/DXZg3NdAaQmZmZmZmZmVnBFSoDKKa7jyoIp54U3RH97W9/m60b3/EsOvX+VuoVV89cv379AE8DL3EP1TnnnAOEInDKllKPe1zMS/teO/ZEXXfddUAoFDvLLLMAYd+oJxWe1nSfOpbV0wew3XbbAe2btafj79BDD+10HbVZu+22W0O2qdVpf4DQC1je26berLjnfPDgwRXXKS9cW+nvFYEyDfTZpp9++pLn4/ZNvXRdZdK0In02nbO0f8RZG/pMtT6vnXbaaUCYorvStNzq6ax31mS1VIyyUjF9HRfaJ3TtNHLkyGydmWaaCQjFt5WtecABBwDt2z7HlEWuXlodL/H/r/YpFchWMdPXX389W0f7hdoxXReoMHg86YYyszTtubI4lGl85JFHZuuqV36rrbYCQlHkVp8WXvtXpWmzlcGh4r36/JUyE/U5tQ8OGDCgw98rmjgOcRYxhP100003beg2NUr5/hLHorx4fHzN2BMq2P7pp58CsOeee3ZYRxm22nebSW3UZZddVrI8PiZUTLs8wz5epzyTsfz4q3SMlf+/6PiMJ8U566yzgDDhUBGKk49PPHnCX//6VyBcD6jYfaMVP+pmZmZmZmZmZr3ceLsIkiSZHbgGmBFIgcFpmp6bJMm0wE1AH2A0sFWapl/Xb1Oro3HVqtWiXhz1WO28887ZurWeIrCV6U5uPB5Td2hVa0TTBttYmiYR4PHHHwdg1llnBWDttdcGwt3uuMe5q2mWW5WOm+uvvx4I2Tcag19rcY/CU089BYRaGspC0nsfccQR2brx1KbtSL3B6lGKqRaQMn9avSe33tSbdcMNN2TL9tlnn5J1JptsMqBjfQAI+3A5ZegVPQNI1ParDVOs4owYrVNp2thWpkwTTQ2sDIn4/1bHmrIZuyP+e5re9qabbgJCVp/2oUGDBmXrrrbaat1+z2bR51D2xUcffQSEqeMhnAd/85vfAGGq9CJk/kifPn2AMOW9MiviY+Tpp58Guq4BpFpje++9NxBq1qj3W9Mwx8vKe8aVXaBtgXAu/Pzzz4EwNbNqDbUqZWeUZ+1B+Axq+1WvTcdffBwqI03X9r0hcz3O4C+fXnuqqaYCinsu036j4zK+Phe18T3NANL3R2XZ/e53v+t03RNOOAFojes1bYPq/P3lL3/psI6uMysdU3l19Rpl/Dz22GNA6XGpmmm9IfOnPJMW4LbbbgNgkUUWAZp3rOaJ/s/A79M0XRj4NbBvkiQLAwOBoWmazgcMHfe7mZmZmZmZmZm1mPHeqkzT9BPgk3GPv0uSZBQwK7AJsPq41a4GHgUOr8tW5hTfjbzggguA0COonoSHH34YKO1t6Q1091F3qWOLL744EHrVi9pzUC1lw4wYMSJbpjGb66yzDgALLrggEHqo4hpAlaretzp95tGjRwPhmFKvb1xbpbx+TaUsofJaIvo7lbI61JNVXhlfPewbbbRRtR+n5SgrTNl28bF28cUXAyHzpzf0ZOah40gz0kHH7LruZNspW+8f//hHtky1O4pIvaGaSUgZCPExOmbMGCC0Z+plbfVzgnr/la2in6ecckq2zlJLLQWEGZRWXnnlDn+nfEYd7SNaftVVV2XraiZNZcaovdI6ii+097GsWKitjo9DxWXfffcFQkZQkcw222wlv+v/PZ6J64MPPgDC+X/UqFFA6XXmFltsAYT9QvV8qqG6QV1lWCkDpNXpeuHYY48F4Pjjj8+e02xL/fv3B2D99dcHwv427bTTdvh7ysZedNFF67TFzadj8NFHH82W6XpJWR+6RtOsTEWjGeLOPfdcINSRgdBev//++yU/1UZDaa0tCBnCaueuuOKK7Dllpf3+978veY1ife2112bLNttss5LnWoHqi6lmUVyT7Oqrry75qRo1ler/6fyl0TK6LtAxF9M11Oabbw4U65ygmTAr/R+Xt8ka0aBr08MOOyx7TvHUd6hmjUKqKv8qSZI+wFLAs8CM424OAYxh7BCxSq/ZM0mSEUmSjFCKqlXmWOXnWFXH8crPscrPscrPscrPsaqO45WfY5WfY5WfY1Udxys/xyo/xyq/3LcqkySZHLgVOChN02/jHsE0TdMkSSoOBkzTdDAwGKBv377VDzKsQjw7hXo5dQd3/vnnB1q7J66esXrzzTeB0p4D2XjjjYHW7+WNNWK/0l3euBdv2WWXLVlHdSXU8xBnIrTSTDp546VeyYEDx47o1Dhh9TJpFrRxf7PktcoeUO8nwMsvvwyEuKhnr6uxw8qyUpbMhhtuCDRuvHA99y1lIA4dOhQImQsQ6mi0chtVrhHHoXqKt99++2xZefsuXbVh5fvc8OHDAVhzzTWzZfXMAGrkubAS7Vc6jirV3tDjt956CwgzZDb63FBtrPSZVl99dQB+/etfA6W1MtSOqH3RzI2ayRFCO6eePs2aqfYrnjFNVJdFPXzbbrst0Nie4HruW8qQu//++wF4/vnns+e23nproL1qOXQ3VurJ3mWXXYDS83ueNrvSbFfdpf0UQuyV9VY+y19P1HO/UruueMQZxPp8um5VLRZ91gsvvDBbVz3vlTL6GqmesSqv3RlnnigmqquibKlWv57vbrz0eXWu1rUqwOGHjx2Eokw8ZespYwXg3nvvLfl7uqbV7Hx5somVZRVfv+n4q4ee7lvKBlMdMgjnemXqVPrc5e16+SyWldq9zuqXNUo9jkPFRu2OshXjGZ51faqYvPrqq0CY8Su+HlAtKR2rzZLrfyhJkgkZe/PnujRNbxu3+NMkSWYe9/zMwGedvd7MzMzMzMzMzJpnvDeAkrG3kS8HRqVpelb01F1A/3GP+wN31n7zzMzMzMzMzMysp/LkKK8E7Ai8nCSJ8qmPBE4Dbk6SZDfgPWCr+mzi+KmI1ZVXXpktK5++WtNax+nevYGGPcSFG6E0PXSeeeYBil0AtTuUWjznnHNmyxQjFX3UTxVPjqed7OkUlM2gNEUVvRs2bBgADz30EADffPNNtq6Gh2lf0s94qI0eV3oOYOqpp84eq3ilCh2usMIKPf04LUOp7CrGp6Ek8fSccTqpBdp34qGYGqL05z//GQjDM9S+a2gmhOKGKs6r1GQVOn7mmWeydWefffaab3+rKI9jpSFgGmKg41zn0Xqmt9eShkFo8oebb745e07DATQ8QAUx48KY5UO8yn/X1LYQhtpoSm5Nx100GgKm4yima4d4aEFR6fjRObKZxV6nmGKK7PGNN94ItEeZg9hkk00GhELP8bA2TfygYeVaV0N/NfQQ2vM6q1q6vtTQk3iCEcXm7LPPBppXULbRNIX2V199lS3T/nHIIYcA4diIv+/ofKdleYZ8aeiTCk+rkLsKUreLOA76fqOfveE46i7tMyqhorbq3XffzdY57bTTgHA86ruO7kEcffTR2braf5od8zyzgD0JdDaYdK3abo6ZmZmZmZmZmdVa68xX1w2686u744899lj2nHpnjjjiCACWWWaZBm9da1Gmhe4AH3PMMdlzuhvZToUcG0nFQiFkA6kH7s47x458VKbMgAEDsnVbvQhfV1TY9O677wZCMee48OUqq6wCdOxBibN8tE+pt1xTnev4XG+99Tq8d1dT3LYrxU0ZY9o32qW3ttWoqOORRx4JdGy74qwWFblXz6gK+d1zzz0lz0PIeCniPijlRVfj3mQdu88++ywQeufbhdqVFVdcESg97yujUMfiHnvsAZS2X+VZHdoPVOA5ng5YcVSPX1HFWZ/lVl11VaD4MWg18RTymn66Xa/fVEh30KBB2TJNACH6bOXZxr2FjsH33nsPKL3GUnZKb7uW0D6hNgjCuUxTue++++5A5YkO8lCW+3777QeETFhdH1vvoGslFQzv168fAKNHj+6w7rTTTguELGRdQ6jYNjQ3ezTWnmcMMzMzMzMzMzPLrTVuQ1VJPXgaJ7zPPvt0WOfkk08GYP/992/chrUg9ZSoJ1Q9CHG9kVa5G9mq4t6mTTfdFAiZP8psmXvuuYHSujVx5lC70r6x1FJLdXjuk08+AUImnnql4roZquuju+K9dV/TWN/55psPCO3T8ssv37RtKoLOer3j/UyP9957bwBeeeUVAO644w4gZIJAOH6V6VfEnmbVS1KWzJAhQzqss8ACCwDte7xqv4jH2Gt6dvUAa1p41QSK19dP9arHU1T3NoqP2vK4btlKK60EFPM4aWVxvNv1GC3X2zJYqqHMFk3n/cMPP2TP6Vp05ZVXbvyGtYD4WFh33XUB+O6774BQ72748OHZOqrfcs011wDw0UcfAeG69ZZbbsnWXXvttTu8h/U+up5YcMEFAXjppZcAeOedd7J1VB9Qx6h+b+XMzNbdMjMzMzMzMzMzq4m27DrQndrDDz+8ZHl8l1bjQttl9pJ60zjhIs9y0wi6m7vOOusAoRdd4tlQit5roLpS8UxeVpkySl588UXA7VIzKINBmUBPPPEEUDqLyNtvvw2E7L0iHsPqmVI9m0ceeSR7Tr2jqnFQxM+vjAnN6OjZL7um2c00815cf6aI+4dZq1GdpOeffx4oPe6UcWCBanQut9xyACy99NLZc8pUV10fKa+NZ9YZXR8tscQSTd6SnnEGkJmZmZmZmZlZwfkGkJmZmZmZmZlZwbXlEDBN26pUfhXzuuyyy7J1lPrXygWYrH0pBdDTQVoeSi+Oi9JaYym1W4Wer7rqKiAUxodQLL/I5w0NB1bR0LgI9GOPPQbAaqutBoQpTK338rnOrLk0bFc/LR+dx3XOM7OguFe5ZmZmZmZmZmYGtGkGkIp46W74gAEDgNJpJIvcg2tmZt3z3//+FwiFuOOsLBWP7E3ic6Uy1TR9rpmZmZkVi++SmJmZmZmZmZkVXKJsmoa8WZJ8DvwAfNGwN+256ej59s6Zpun01bzAscqvTWMFPY9X1bGCto2XY5Wfj8P8HKv8mhmr92r0/o3kNis/xyo/t1n5uc2qjo/D/Byr/Nxm5dewWDX0BhBAkiQj0jTt29A37YFmbq9j1R7v3V2OV36OVX6OVX6OVX7N3t5mv3+1vG/l51jl51jl1+ztbfb7V8v7Vn6OVX6OVX6N3F4PATMzMzMzMzMzKzjfADIzMzMzMzMzK7hm3AAa3IT37Ilmbq9j1R7v3V2OV36OVX6OVX6OVX7N3t5mv3+1vG/l51jl51jl1+ztbfb7V8v7Vn6OVX6OVX4N296G1wAyMzMzMzMzM7PG8hAwMzMzMzMzM7OC8w0gMzMzMzMzM7OCa9gNoCRJ1k+S5I0kSd5OkmRgo943ryRJZk+S5JEkSV5LkuTVJEkOHLd82iRJHkyS5K1xP6dp0PY4Xvm3xbHKvy2OVf5tcayq2x7HK/+2OFb5t8Wxyr8tjlV12+N45d8Wxyr/tjhW+bfFscq/LS0dK3C8qtH0WKVpWvd/wC+Ad4C5gYmAF4GFG/HeVWzjzMDS4x5PAbwJLAycAQwct3wgcLrj1TrxcqwcK8fKbVY7xcuxcqwcK7dZ7RQvx8qxcqwcK8erWLFqVAbQcsDbaZr+PU3TfwM3Aps06L1zSdP0kzRN/zbu8XfAKGBWxm7n1eNWuxrYtAGb43jl51jl51jl51hVx/HKz7HKz7HKz7GqjuOVn2OVn2OVn2OVX8vHChyvajQ7Vo26ATQr8EH0+4fjlrWkJEn6AEsBzwIzpmn6ybinxgAzNmATHK/8HKv8HKv8HKvqOF75OVb5OVb5OVbVcbzyc6zyc6zyc6zya6tYgeNVjWbEykWgyyRJMjlwK3BQmqbfxs+lY/Ox0qZsWItyvPJzrPJzrPJzrKrjeOXnWOXnWOXnWFXH8crPscrPscrPsaqO45Vfs2LVqBtAHwGzR7/PNm5ZS0mSZELG/idcl6bpbeMWf5okyczjnp8Z+KwBm+J45edY5edY5edYVcfxys+xys+xys+xqo7jlZ9jlZ9jlZ9jlV9bxAocr2o0M1aNugE0HJgvSZK5kiSZCNgGuKtB751LkiQJcDkwKk3Ts6Kn7gL6j3vcH7izAZvjeOXnWOXnWOXnWFXH8crPscrPscrPsaqO45WfY5WfY5WfY5Vfy8cKHK9qND1WaeOqXfdjbIXrd4CjGvW+VWzfyoxNs3oJeGHcv37Ar4ChwFvAQ8C0jldrxcuxcqwcK7dZ7RQvx8qxcqzcZrVTvBwrx8qxcqwcr+LEKhm3EWZmZmZmZmZmVlAuAm1mZmZmZmZmVnC+AWRmZmZmZmZmVnC+AWRmZmZmZmZmVnC+AWRmZmZmZmZmVnC+AWRmZmZmZmZmVnC+AWRmZmZmZmZmVnC+AWRmZmZmZmZmVnD/D/06NpiAq+99AAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<Figure size 1440x180 with 30 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "##########################\n",
    "### VISUALIZATION\n",
    "##########################\n",
    "\n",
    "n_images = 15\n",
    "image_width = 28\n",
    "\n",
    "fig, axes = plt.subplots(nrows=2, ncols=n_images, \n",
    "                         sharex=True, sharey=True, figsize=(20, 2.5))\n",
    "orig_images = features[:n_images]\n",
    "decoded_images = decoded[:n_images]\n",
    "\n",
    "for i in range(n_images):\n",
    "    for ax, img in zip(axes, [orig_images, decoded_images]):\n",
    "        curr_img = img[i].detach().to(torch.device('cpu'))\n",
    "        ax[i].imshow(curr_img.view((image_width, image_width)), cmap='binary')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "numpy       1.15.4\n",
      "torch       1.0.0\n",
      "\n"
     ]
    }
   ],
   "source": [
    "%watermark -iv"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.1"
  },
  "toc": {
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
